<!DOCTYPE html>
<html class="no-js" lang="zh">
  <head>
<meta charset="utf-8">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no">
<link rel="alternate" href="https://www.desgard.com" hreflang="pt-BR">
<link href="http://gmpg.org/xfn/11" rel="profile">
<meta name="HandheldFriendly" content="True">
<meta name="MobileOptimized" content="320">
<meta name="theme-color" content="#005344">
<title>Guardia · 瓜地</title>
<meta name="google-site-verification" content="zS1dSn20XtA4FJYEOQLXqI0boxZdMnJ2g3beje-cl20">
<meta name="description" content="I write many code to write less code.💻">
<meta name="keywords" content="">
<!-- Social: Facebook / Open Graph -->
<meta property="og:url" content="https://www.desgard.com/Shadowsocks-1/">
<meta property="og:title" content="       Shadowsocks Probe I - Sock5 与 EventLoop 事件分发 | Gua  ">
<meta property="og:description" content="I write many code to write less code.💻">
<meta property="og:site_name" content="Desgard_Duan">
<meta property="og:locale" content="pt_BR">
<meta property="og:type" content="website">
<meta property="og:author" content="https://www.facebook.com/desgard.duan">
<meta property="og:image" content="https://www.desgard.com">
<!-- Social: Twitter -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:site" content="@nandomoreirame">
<meta name="twitter:domain" content="https://www.desgard.com">
<meta name="twitter:title" content="       Shadowsocks Probe I - Sock5 与 EventLoop 事件分发 | Gua  ">
<meta name="twitter:description" content="I write many code to write less code.💻">
<meta name="twitter:image:src" content="https://www.desgard.com">
<!-- Favicons -->
<link rel="apple-touch-icon" sizes="114x114" href="https://www.desgard.com/assets/ico/apple-touch-icon-114-516f4e19976b9e4dbb77ad9b576831fe.png">
<link rel="apple-touch-icon" sizes="72x72" href="https://www.desgard.com/assets/ico/apple-touch-icon-72-5409b2df229305703caf583d86c845ab.png">
<link rel="apple-touch-icon" href="https://www.desgard.com/assets/ico/apple-touch-icon-57-aa873e019cf659e0d4e6a0b5bb9f379d.png">
<link rel="shortcut icon" href="https://www.desgard.com/assets/ico/favicon-4298be3d3fbe23e18d65bded9d930899.png">
<!-- rel prev and next -->
<!-- Canonical link tag -->
<link rel="canonical" href="https://www.desgard.com/Shadowsocks-1/">
<link rel="alternate" type="application/rss+xml" title="Gua" href="https://www.desgard.com/feed.xml">
<link rel="stylesheet" href="/assets/main-0b7b828712f4c43b75bba4861c907bb1.css">
<script src="/assets/modernizr-custom-cb807611a3e262b2eac59444cbab74d6.js" data-cfasync="false"></script>
<script src="/assets/js/jquery-3.1.js"></script>
<script src="/assets/js/jquery.rotate.js"></script>
<script type="text/javascript" src="https://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
<script type="application/ld+json">
{
  "@context": "http://schema.org",
  "@type": "Website",
  "publisher": "www.desgard.com",
  "url": "http://www.desgard.com/",
  "description": "瓜地"
}
</script>
<script type="text/javascript">
  var disqus_shortname = 'desgard',
      baseurl          = '';
</script>
<!--
<script type="text/javascript">
var _gaq = _gaq || [];
_gaq.push(['_setAccount', 'UA-52446115-1']);
_gaq.push(['_trackPageview']);
(function() {
  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
})();
</script>-->
<script>
var _hmt = _hmt || [];
(function() {
  var hm = document.createElement("script");
  hm.src = "//hm.baidu.com/hm.js?c5a8123bc51782a3083a631ed9ff50e4";
  var s = document.getElementsByTagName("script")[0]; 
  s.parentNode.insertBefore(hm, s);
})();
</script>
<script type="text/x-mathjax-config">
    MathJax.Hub.Config({
      tex2jax: {
        inlineMath: [ ['$','$'] ],
        displayMath: [ ['$$','$$'] ],
        processEscapes: true
      }
    });
  </script>
  </head>
  <body class="post-template single">
    <header class="header">
  <div class="container">
    <h1><a href="/"><strong>desgard</strong>.com</a></h1>
    <nav class="navbar">
      <ul>
        <li><a href="/">Home</a></li>
        <li><a href="#!" data-modal="modalSearch"><i class="fa fa-search"></i></a></li>
        <li><a href="/comment " target="_blank"><i class="fa fa-comments"></i></a></li>
        <li><a href="/feed.xml" target="_blank"><i class="fa fa-feed"></i></a></li>
      </ul>
    </nav>
  </div>
</header>
    <main class="wrapper container" itemprop="mainContentOfPage" itemscope="itemscope" itemtype="http://schema.org/Blog">
      <article class="post" itemscope="itemscope" itemtype="http://schema.org/BlogPosting" itemprop="blogPost">
  <header class="post-header">
    <h1>Shadowsocks Probe I - Sock5 与 EventLoop 事件分发</h1>
    <div class="post-meta">
      <time datetime="2015-10-13">
        <i class="fa fa-calendar-o"></i> <time datetime="2017-08-14"> 2017-08-14
      </time>
      <span>
        <i class="fa fa-comments"></i> <a href="#container">Comment me!</a>
      </span>
      <span>
<!--
        <i class="fa fa-folder-open-o"></i> 
-->
      </span>
    </div>
  </header>
  <h2 id="define--">#define 爱国 科学</h2>
<p>最近 Apple Store 在大陆下架了所有 VPN 应用。然而日常的爱国上网已经成为了刚需。这也就是促使我阅读 Shadowsocks 源码的原因。希望后期可以自行编写移动设备的 Client 端而努力。</p>
<h2 id="shadowsocks-">Shadowsocks 的原理初探</h2>
<p>关于 Shadowsocks 的原理，有一张经典的图示解释的十分清晰：</p>
<p><img src="../assets/images/blog/15024138759233/whats-shadowsocks-041.png" alt="whats-shadowsocks-041" /></p>
<p>（该图引用自 <a href="http://vc2tea.com/whats-shadowsocks/">vc2tea · 写给非专业人士看的 Shadowsocks 简介</a>）</p>
<p>Shadowsocks 是将原先的 ssh 创建的 Sock5 协议分成了 Server 端和 Client 端，这是一种类 <strong>ssh tunnel</strong> 的解决方案。</p>
<p>客户端发出的 Socks5 协议与 SS Local 进行通信以后，由于 SS Local 是当前使用端或是一个路由器越过 GFW，与 SS Server 进行通信，避免了 GFW 的分析干扰问题。并且在 SS Local 和 SS Server 两端可通过各种各样的加密方式进行通信，并且经过 GFW 的网络包就是很普通的 TCP 包，没有特征码，GFW 也无法对其数据进行解密。SS Server 对数据进行解密，还原请求并触发，在以相同的通信方式回传 SS Local。</p>
<p>一句话总结就是 Shadowsocks 可以加密数据包并伪装成常规的 TCP 包，从而达到数据交互。</p>
<h2 id="socks5-">Socks5 协议</h2>
<p><a href="https://loggerhead.me/posts/shadowsocks-yuan-ma-fen-xi-xie-yi-yu-jie-gou.html">Shadowsocks 源码分析——协议与结构</a> 这篇文中讲述了 Socks5 协议的三个过程：<strong>握手阶段</strong>、<strong>建立连接</strong>、<strong>传输阶段</strong>。再具体一些可将其扩展成这么一个工作流：</p>
<ol>
  <li>Client 向 Proxy 发出请求信息，用以写上传输方式；</li>
  <li>Proxy 做出应答；</li>
  <li>Client 接到应答后向 Proxy 发送 <em>Destination Server</em> （很多书中称之为<strong>目的主机</strong>）的 IP 和 Port；</li>
  <li>Proxy 来评估 <em>Destination Server</em> 的主机地址，并返回自身的 IP 和 Port，此时 C/P 的链接建立；</li>
  <li>Proxy 和 Dst Server 链接；</li>
  <li>Proxy 将 Client 发出的信息传至 Server，将 Server 响应的信息转发给 Client，完成整个代理过程。</li>
</ol>
<p>在 Client 连接 Proxy 的时候，通过第一个报文信息来<strong>协商认证</strong>，比如其中的信息包括：是否使用用户名/密码方式进行认证等等。以下是格式信息，数字表示对应字段占用的 Byte 值：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">+----+----------+----------+</span>
<span class="o">|</span><span class="no">VER</span> <span class="o">|</span> <span class="no">NMETHODS</span> <span class="o">|</span> <span class="no">METHODS</span>  <span class="o">|</span>
<span class="o">+----+----------+----------+</span>
<span class="o">|</span> <span class="mi">1</span>  <span class="o">|</span>    <span class="mi">1</span>     <span class="o">|</span>  <span class="mi">1</span><span class="o">~</span><span class="mi">255</span>   <span class="o">|</span>
<span class="o">+----+----------+----------+</span></code></pre></div>
<ul>
  <li><code>VER</code>：是当前协议的版本号，这里是 <code>5</code>；</li>
  <li><code>NMETHODS</code>：是 <code>METHODS</code> 字段占用的 Byte 数；</li>
  <li><code>METHOD</code>：每一个字节表示一种认证方式，表示客户端支持的全部认证方式。</li>
</ul>
<p>Proxy 在收到客户端请求后，检查是否有认证方式，并返回一下格式的消息：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">+----+--------+</span>
<span class="o">|</span><span class="no">VER</span> <span class="o">|</span> <span class="no">METHOD</span> <span class="o">|</span>
<span class="o">+----+--------+</span>
<span class="o">|</span> <span class="mi">1</span>  <span class="o">|</span>   <span class="mi">1</span>    <span class="o">|</span>
<span class="o">+----+--------+</span></code></pre></div>
<p>对于 Shadowsocks 而言，只有两种可能：</p>
<ul>
  <li><code>0x05 0x00</code>：告诉 Client 采用无认证方式来建立连接；</li>
  <li><code>0x05 0xff</code>：客户端的任意一种认证方式 Proxy 都不支持。</li>
</ul>
<p><img src="../assets/images/blog/15024138759233/handshake-time.png" alt="handshake-time" />
在握手之后，Client 会向 Proxy 发送请求，格式如下：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">+----+-----+-------+------+----------+----------+</span>
<span class="o">|</span><span class="no">VER</span> <span class="o">|</span> <span class="no">CMD</span> <span class="o">|</span>  <span class="no">RSV</span>  <span class="o">|</span> <span class="no">ATYP</span> <span class="o">|</span> <span class="no">DST</span><span class="o">.</span><span class="n">ADDR</span> <span class="o">|</span> <span class="no">DST</span><span class="o">.</span><span class="n">PORT</span> <span class="o">|</span>
<span class="o">+----+-----+-------+------+----------+----------+</span>
<span class="o">|</span> <span class="mi">1</span>  <span class="o">|</span>  <span class="mi">1</span>  <span class="o">|</span>   <span class="mi">1</span>   <span class="o">|</span>  <span class="mi">1</span>   <span class="o">|</span> <span class="no">Variable</span> <span class="o">|</span>    <span class="mi">2</span>     <span class="o">|</span>
<span class="o">+----+-----+-------+------+----------+----------+</span></code></pre></div>
<ul>
  <li><code>CMD</code>：一些配置标识，Shadowsocks 只用到了以下两种：
    <ul>
      <li><code>0x01</code>：建立 TCP 连接；</li>
      <li><code>0x03</code>：关联 UDP 请求；</li>
    </ul>
  </li>
  <li><code>RSV</code>：保留字段，值为 <code>0x00</code>；</li>
  <li><code>ATYP</code>：<code>address type</code> 的缩写，取值为：
    <ul>
      <li><code>0x01</code>：IPv4；</li>
      <li><code>0x03</code>：域名；</li>
      <li><code>0x04</code>：IPv6</li>
    </ul>
  </li>
  <li><code>DST.ADDR</code>：<code>destination address</code> 的缩写，取值会随着 <code>ATYP</code> 变化：
    <ul>
      <li><code>ATYP == 0x01</code>：4 个字节的 IPv4 地址；</li>
      <li><code>ATYP == 0x03</code>：1 个字节表示域名长度，紧随其后的是对应的域名；</li>
      <li><code>ATYP == 0x04</code>：16 个字节的 IPv6 地址；</li>
    </ul>
  </li>
  <li><code>DST.PORT</code> 字段：目的服务器端口号。</li>
</ul>
<p>在收到请求后，Proxy 也会对应的返回如下格式的消息：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">+----+-----+-------+------+----------+----------+</span>
<span class="o">|</span><span class="no">VER</span> <span class="o">|</span> <span class="no">REP</span> <span class="o">|</span>  <span class="no">RSV</span>  <span class="o">|</span> <span class="no">ATYP</span> <span class="o">|</span> <span class="no">BND</span><span class="o">.</span><span class="n">ADDR</span> <span class="o">|</span> <span class="no">BND</span><span class="o">.</span><span class="n">PORT</span> <span class="o">|</span>
<span class="o">+----+-----+-------+------+----------+----------+</span>
<span class="o">|</span> <span class="mi">1</span>  <span class="o">|</span>  <span class="mi">1</span>  <span class="o">|</span>   <span class="mi">1</span>   <span class="o">|</span>  <span class="mi">1</span>   <span class="o">|</span> <span class="no">Variable</span> <span class="o">|</span>    <span class="mi">2</span>     <span class="o">|</span>
<span class="o">+----+-----+-------+------+----------+----------+</span></code></pre></div>
<p><code>REP</code> 字段是用来告知 Client 请求的处理情况，正常情况下 Shadowsocks 会将其填充为 <code>0x00</code>，否则直接断开连接。其他的字段含义均同发送包的字段含义相同。</p>
<p>在万事具备之后，Socks5 协议就完成了自身的主要实名，在握手和建立连接之后，Socks5 的 Proxy 服务器就只做简单的消息转发。我们以通过 Shadowsocks 代理来访问 <code>apple.com:80</code> 为例，整个过程如下图所示：</p>
<p><img src="../assets/images/blog/15024138759233/visit-apple.com.re.png" alt="visit-apple.com.re" /></p>
<p>而信息的传输过程可能是这样的：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># 握手阶段</span>
<span class="c1"># 无验证最简单的握手</span>
<span class="n">client</span> <span class="o">-&gt;</span> <span class="ss">ss</span><span class="p">:</span> <span class="mh">0x05</span> <span class="mh">0x01</span> <span class="mh">0x00</span> 
<span class="n">ss</span> <span class="o">-&gt;</span> <span class="ss">client</span><span class="p">:</span> <span class="mh">0x05</span> <span class="mh">0x00</span>
<span class="c1"># 建立连接</span>
<span class="c1"># b&#39;apple.com&#39; 表示 &#39;apple.com&#39; 的 ASCII 码</span>
<span class="n">client</span> <span class="o">-&gt;</span> <span class="ss">ss</span><span class="p">:</span> <span class="mh">0x05</span> <span class="mh">0x01</span> <span class="mh">0x00</span> <span class="mh">0x03</span> <span class="mh">0x0a</span> <span class="n">b</span><span class="s1">&#39;apple.com&#39;</span>  <span class="mh">0x00</span> <span class="mh">0x50</span>
<span class="n">ss</span> <span class="o">-&gt;</span> <span class="ss">client</span><span class="p">:</span> <span class="mh">0x05</span> <span class="mh">0x00</span> <span class="mh">0x00</span> <span class="mh">0x01</span> <span class="mh">0x00</span> <span class="mh">0x00</span> <span class="mh">0x00</span> <span class="mh">0x00</span> <span class="mh">0x10</span> <span class="mh">0x10</span>
<span class="c1"># 传输阶段</span>
<span class="n">client</span> <span class="o">-&gt;</span> <span class="n">ss</span> <span class="o">-&gt;</span> <span class="n">remote</span>
<span class="n">remote</span> <span class="o">-&gt;</span> <span class="n">ss</span> <span class="o">-&gt;</span> <span class="n">client</span>
<span class="o">.</span><span class="n">.</span><span class="o">.</span></code></pre></div>
<h2 id="shadowsocks--1">Shadowsocks 模块划分</h2>
<p>在真正深入到源码之前，先看看各个模块的主要功能划分：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="o">.</span><span class="p">(</span><span class="n">shadowsocks</span><span class="p">)</span>
<span class="err">├──</span> <span class="n">__init__</span><span class="o">.</span><span class="n">py</span>
<span class="err">├──</span> <span class="n">asyncdns</span><span class="o">.</span><span class="n">py</span> <span class="c1"># 实现了简单的异步 DNS 查询</span>
<span class="err">├──</span> <span class="n">common</span><span class="o">.</span><span class="n">py</span> <span class="c1"># 提供一些工具函数，重要的是解析 Socks5 请求</span>
<span class="err">├──</span> <span class="n">crypto</span> <span class="c1"># 封装加密库的调用</span>
<span class="err">│  </span> <span class="err">├──</span> <span class="n">__init__</span><span class="o">.</span><span class="n">py</span>
<span class="err">│  </span> <span class="err">├──</span> <span class="n">openssl</span><span class="o">.</span><span class="n">py</span>
<span class="err">│  </span> <span class="err">├──</span> <span class="n">rc4_md5</span><span class="o">.</span><span class="n">py</span>
<span class="err">│  </span> <span class="err">├──</span> <span class="n">sodium</span><span class="o">.</span><span class="n">py</span>
<span class="err">│  </span> <span class="err">├──</span> <span class="n">table</span><span class="o">.</span><span class="n">py</span>
<span class="err">│  </span> <span class="err">└──</span> <span class="n">util</span><span class="o">.</span><span class="n">py</span>
<span class="err">├──</span> <span class="n">daemon</span><span class="o">.</span><span class="n">py</span> <span class="c1"># 用于实现守护进程(daemon)</span>
<span class="err">├──</span> <span class="n">encrypt</span><span class="o">.</span><span class="n">py</span> <span class="c1"># 提供统一的加密和解密接口</span>
<span class="err">├──</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">py</span> <span class="c1"># 封装了 IO 常用方法 epoll, kqueue 和 select ，提供统一接口</span>
<span class="err">├──</span> <span class="n">local</span><span class="o">.</span><span class="n">py</span> <span class="c1"># shadowsocks 客户端入口 - sslocal 命令</span>
<span class="err">├──</span> <span class="n">lru_cache</span><span class="o">.</span><span class="n">py</span> <span class="c1"># LRU Cache，说白了就是限时缓存，过量删除的一种机制</span>
<span class="err">├──</span> <span class="n">manager</span><span class="o">.</span><span class="n">py</span> <span class="c1"># 总控入口，用于组织组件逻辑</span>
<span class="err">├──</span> <span class="n">server</span><span class="o">.</span><span class="n">py</span> <span class="c1"># shadowsocks 服务端 - ssserver 命令</span>
<span class="err">├──</span> <span class="n">shell</span><span class="o">.</span><span class="n">py</span> <span class="c1"># shell 命令封装包</span>
<span class="err">├──</span> <span class="n">tcprelay</span><span class="o">.</span><span class="n">py</span> <span class="c1"># 核心部分，实现整个 Socks5 协议，负责 TCP 代理部分</span>
<span class="err">└──</span> <span class="n">udprelay</span><span class="o">.</span><span class="n">py</span> <span class="c1"># 负责 UDP 代理实现</span></code></pre></div>
<p>Shadowsocks 利用 Socks5 协议来进行数据传输，而增加的一个过程就是对 TCP 包的数据加密。这里我们称之为能爱国上网的 Socks5 代理：</p>
<p><img src="../assets/images/blog/15024138759233/patriotic-networ-2.png" alt="patriotic-networ-2" /></p>
<p>在加密解密过程中，数据经过 <em>sslocal</em> 加密后转发给 <em>ssserver</em>，这是过程中最重要的环节。然后我们开始对细节进行剖析。</p>
<h2 id="serverpy-">server.py 一个通往爱国的大门</h2>
<p>我们从 <code>server.py</code> 这个入口函数开始看起，这样也便于把握整体代码的流程。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">main</span><span class="p">():</span>
    <span class="c1"># 配置代码</span>
    <span class="c1"># 检测 python 版本</span>
    <span class="n">shell</span><span class="o">.</span><span class="n">check_python</span><span class="p">()</span>
    <span class="c1"># 从命令行中获得配置参数</span>
    <span class="n">config</span> <span class="o">=</span> <span class="n">shell</span><span class="o">.</span><span class="n">get_config</span><span class="p">(</span><span class="no">False</span><span class="p">)</span>
    <span class="c1"># 根据配置决定要不要以 daemon 的方式运行</span>
    <span class="n">daemon</span><span class="o">.</span><span class="n">daemon_exec</span><span class="p">(</span><span class="n">config</span><span class="p">)</span>
    <span class="c1"># 端口加密模式输出 log</span>
    <span class="o">.</span><span class="n">.</span><span class="o">.</span>
    <span class="n">tcp_servers</span> <span class="o">=</span> <span class="o">[]</span>
    <span class="n">udp_servers</span> <span class="o">=</span> <span class="o">[]</span>
    <span class="c1"># dns 服务器配置</span>
    <span class="k">if</span> <span class="s1">&#39;dns_server&#39;</span> <span class="k">in</span> <span class="ss">config</span><span class="p">:</span>  <span class="c1"># allow override settings in resolv.conf</span>
        <span class="n">dns_resolver</span> <span class="o">=</span> <span class="n">asyncdns</span><span class="o">.</span><span class="n">DNSResolver</span><span class="p">(</span><span class="n">config</span><span class="o">[</span><span class="s1">&#39;dns_server&#39;</span><span class="o">]</span><span class="p">,</span>
                                            <span class="n">config</span><span class="o">[</span><span class="s1">&#39;prefer_ipv6&#39;</span><span class="o">]</span><span class="p">)</span>
    <span class="k">else</span><span class="p">:</span>
        <span class="n">dns_resolver</span> <span class="o">=</span> <span class="n">asyncdns</span><span class="o">.</span><span class="n">DNSResolver</span><span class="p">(</span><span class="n">prefer_ipv6</span><span class="o">=</span><span class="n">config</span><span class="o">[</span><span class="s1">&#39;prefer_ipv6&#39;</span><span class="o">]</span><span class="p">)</span>
    <span class="c1"># 将 port password 存入缓存,从配置字典中删除</span>
    <span class="n">port_password</span> <span class="o">=</span> <span class="n">config</span><span class="o">[</span><span class="s1">&#39;port_password&#39;</span><span class="o">]</span>
    <span class="n">del</span> <span class="n">config</span><span class="o">[</span><span class="s1">&#39;port_password&#39;</span><span class="o">]</span>
    <span class="c1"># 从配置中读入每一组配置信息</span>
    <span class="k">for</span> <span class="n">port</span><span class="p">,</span> <span class="n">password</span> <span class="k">in</span> <span class="n">port_password</span><span class="o">.</span><span class="n">items</span><span class="p">():</span>
        <span class="n">a_config</span> <span class="o">=</span> <span class="n">config</span><span class="o">.</span><span class="n">copy</span><span class="p">()</span>
        <span class="n">a_config</span><span class="o">[</span><span class="s1">&#39;server_port&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="n">int</span><span class="p">(</span><span class="n">port</span><span class="p">)</span>
        <span class="n">a_config</span><span class="o">[</span><span class="s1">&#39;password&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="n">password</span>
        <span class="n">logging</span><span class="o">.</span><span class="n">info</span><span class="p">(</span><span class="s2">&quot;starting server at %s:%d&quot;</span> <span class="o">%</span>
                     <span class="p">(</span><span class="n">a_config</span><span class="o">[</span><span class="s1">&#39;server&#39;</span><span class="o">]</span><span class="p">,</span> <span class="n">int</span><span class="p">(</span><span class="n">port</span><span class="p">)))</span>
        <span class="c1"># 在做完备份至内存后实例化一个 tcprelay.TCPRelay 并放入指定容器中</span>
        <span class="n">tcp_servers</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">tcprelay</span><span class="o">.</span><span class="n">TCPRelay</span><span class="p">(</span><span class="n">a_config</span><span class="p">,</span> <span class="n">dns_resolver</span><span class="p">,</span> <span class="no">False</span><span class="p">))</span>
        <span class="n">udp_servers</span><span class="o">.</span><span class="n">append</span><span class="p">(</span><span class="n">udprelay</span><span class="o">.</span><span class="n">UDPRelay</span><span class="p">(</span><span class="n">a_config</span><span class="p">,</span> <span class="n">dns_resolver</span><span class="p">,</span> <span class="no">False</span><span class="p">))</span>
	 <span class="n">run_server</span><span class="p">()</span>  
	 <span class="k">def</span> <span class="nf">run_server</span><span class="p">():</span>
        <span class="ss">try</span><span class="p">:</span>
            <span class="c1"># 创建 EventLoop</span>
            <span class="kp">loop</span> <span class="o">=</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">EventLoop</span><span class="p">()</span>
            <span class="n">dns_resolver</span><span class="o">.</span><span class="n">add_to_loop</span><span class="p">(</span><span class="kp">loop</span><span class="p">)</span>
            <span class="c1"># 观察者模式</span>
            <span class="c1"># epoll/kqueue/select 观察者 socket 的状态</span>
            <span class="c1"># 当 socket 状态法伤变化时,调用消息处理函数</span>
            <span class="c1"># 将已经打开的 socket 注册到 Eventloop 用来监听响应的时间</span>
            <span class="n">list</span><span class="p">(</span><span class="n">map</span><span class="p">(</span><span class="nb">lambda</span> <span class="ss">s</span><span class="p">:</span> <span class="n">s</span><span class="o">.</span><span class="n">add_to_loop</span><span class="p">(</span><span class="kp">loop</span><span class="p">),</span> <span class="n">tcp_servers</span> <span class="o">+</span> <span class="n">udp_servers</span><span class="p">))</span>
            <span class="n">daemon</span><span class="o">.</span><span class="n">set_user</span><span class="p">(</span><span class="n">config</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="s1">&#39;user&#39;</span><span class="p">,</span> <span class="no">None</span><span class="p">))</span>
            <span class="c1"># 启动 循环,等待 shadowsocks 客户端的连接</span>
            <span class="kp">loop</span><span class="o">.</span><span class="n">run</span><span class="p">()</span>
        <span class="n">except</span> <span class="no">Exception</span> <span class="n">as</span> <span class="ss">e</span><span class="p">:</span>
            <span class="n">shell</span><span class="o">.</span><span class="n">print_exception</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
            <span class="n">sys</span><span class="o">.</span><span class="n">exit</span><span class="p">(</span><span class="mi">1</span><span class="p">)</span></code></pre></div>
<p>在 <code>TCPRelay</code> 初始化的时候会根据配置项新建一个 Socket 并绑定至指定端口进行监听。下面列举核心代码：</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="c1"># tcprelay.py</span>
<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="n">config</span><span class="p">,</span> <span class="n">dns_resolver</span><span class="p">,</span> <span class="n">is_local</span><span class="p">,</span> <span class="n">stat_callback</span><span class="o">=</span><span class="no">None</span><span class="p">):</span>
	<span class="c1"># 判断是 SS Local 还是 SS Server，此构造方法两端复用，仅仅是配置文件中的 key 不同</span>
   <span class="k">if</span> <span class="ss">is_local</span><span class="p">:</span>
       <span class="n">listen_addr</span> <span class="o">=</span> <span class="n">config</span><span class="o">[</span><span class="s1">&#39;local_address&#39;</span><span class="o">]</span>
       <span class="n">listen_port</span> <span class="o">=</span> <span class="n">config</span><span class="o">[</span><span class="s1">&#39;local_port&#39;</span><span class="o">]</span>
   <span class="k">else</span><span class="p">:</span>
       <span class="n">listen_addr</span> <span class="o">=</span> <span class="n">config</span><span class="o">[</span><span class="s1">&#39;server&#39;</span><span class="o">]</span>
       <span class="n">listen_port</span> <span class="o">=</span> <span class="n">config</span><span class="o">[</span><span class="s1">&#39;server_port&#39;</span><span class="o">]</span>
   <span class="nb">self</span><span class="o">.</span><span class="n">_listen_port</span> <span class="o">=</span> <span class="n">listen_port</span>
   <span class="n">addrs</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">getaddrinfo</span><span class="p">(</span><span class="n">listen_addr</span><span class="p">,</span> <span class="n">listen_port</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span>
                              <span class="n">socket</span><span class="o">.</span><span class="n">SOCK_STREAM</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SOL_TCP</span><span class="p">)</span>
   <span class="k">if</span> <span class="n">len</span><span class="p">(</span><span class="n">addrs</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
       <span class="k">raise</span> <span class="no">Exception</span><span class="p">(</span><span class="s2">&quot;can&#39;t get addrinfo for %s:%d&quot;</span> <span class="o">%</span>
                       <span class="p">(</span><span class="n">listen_addr</span><span class="p">,</span> <span class="n">listen_port</span><span class="p">))</span>
   <span class="n">af</span><span class="p">,</span> <span class="n">socktype</span><span class="p">,</span> <span class="n">proto</span><span class="p">,</span> <span class="n">canonname</span><span class="p">,</span> <span class="n">sa</span> <span class="o">=</span> <span class="n">addrs</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span>
   <span class="c1"># 创建 Socket</span>
   <span class="n">server_socket</span> <span class="o">=</span> <span class="n">socket</span><span class="o">.</span><span class="n">socket</span><span class="p">(</span><span class="n">af</span><span class="p">,</span> <span class="n">socktype</span><span class="p">,</span> <span class="n">proto</span><span class="p">)</span>
   <span class="n">server_socket</span><span class="o">.</span><span class="n">setsockopt</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">SOL_SOCKET</span><span class="p">,</span> <span class="n">socket</span><span class="o">.</span><span class="n">SO_REUSEADDR</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
   <span class="c1"># 绑定监听端口</span>
   <span class="n">server_socket</span><span class="o">.</span><span class="n">bind</span><span class="p">(</span><span class="n">sa</span><span class="p">)</span>
   <span class="n">server_socket</span><span class="o">.</span><span class="n">setblocking</span><span class="p">(</span><span class="no">False</span><span class="p">)</span>
   <span class="k">if</span> <span class="n">config</span><span class="o">[</span><span class="s1">&#39;fast_open&#39;</span><span class="o">]</span><span class="p">:</span>
       <span class="ss">try</span><span class="p">:</span>
       		<span class="c1"># 设置 Socket 相关选项，这里只有 TCP，后两个参数是选项值的缓冲以及其最大长度</span>
           <span class="n">server_socket</span><span class="o">.</span><span class="n">setsockopt</span><span class="p">(</span><span class="n">socket</span><span class="o">.</span><span class="n">SOL_TCP</span><span class="p">,</span> <span class="mi">23</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
       <span class="n">except</span> <span class="n">socket</span><span class="o">.</span><span class="n">error</span><span class="p">:</span>
           <span class="n">logging</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">&#39;warning: fast open is not available&#39;</span><span class="p">)</span>
           <span class="nb">self</span><span class="o">.</span><span class="n">_config</span><span class="o">[</span><span class="s1">&#39;fast_open&#39;</span><span class="o">]</span> <span class="o">=</span> <span class="no">False</span>
   <span class="c1"># 监听 1024 端口</span>
   <span class="n">server_socket</span><span class="o">.</span><span class="n">listen</span><span class="p">(</span><span class="mi">1024</span><span class="p">)</span>
   <span class="nb">self</span><span class="o">.</span><span class="n">_server_socket</span> <span class="o">=</span> <span class="n">server_socket</span>
   <span class="nb">self</span><span class="o">.</span><span class="n">_stat_callback</span> <span class="o">=</span> <span class="n">stat_callback</span></code></pre></div>
<p><code>TCPRelay</code> 中的 <code>_server_socket</code> 表示的是所监听端口的 Socket。再然后当 <code>TCPRelay</code> 的 <code>handle_event</code> 逻辑便分成了两个部分，如果是 <code>_server_socket</code>，那么客户端请求建立连接，<code>_server_socket</code> 负责接受之后创建的新的 <code>TCPRelayHandler</code>；如果不是，则说明客户端连接的是读写事件，直接分发到对应的 <code>TCPRelayHandler</code> 调用 <code>handle_event</code> 来处理。这一部分的详细代码在后面可以看到。</p>
<h2 id="section">事件处理阶段</h2>
<p>Shadowsocks 封装了三种常见的 IO 复用方法：<code>epoll</code>、<code>kqueue</code> 和 <code>select</code>，并通过 <code>eventloop.py</code> 提供统一的接口。之所以要用 OI 复用，因为能提供更好的性能和更少的内存开销。多线程方式在 PC 上表现良好，但是在路由器上就表现不佳了。</p>
<h3 id="eventlooppy">eventloop.py</h3>
<p><code>eventloop.py</code> 主要逻辑都在 <code>run</code> 方法中。EventLoop 是 Shadowsocks 的网络通信核心模块，它封装了 <code>epoll</code>、<code>kqueue</code>、<code>select</code> 三者，而且为后两者实现了类似 <code>epoll</code> 接口，且执行优先级为 <code>epoll</code> &gt; <code>kqueue</code> &gt; <code>select</code>。</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">run</span><span class="p">(</span><span class="nb">self</span><span class="p">):</span>
   <span class="n">events</span> <span class="o">=</span> <span class="o">[]</span>
   <span class="k">while</span> <span class="ow">not</span> <span class="nb">self</span><span class="o">.</span><span class="n">_stopping</span><span class="p">:</span>
       <span class="n">asap</span> <span class="o">=</span> <span class="no">False</span>
       <span class="c1"># 获取事件</span>
       <span class="ss">try</span><span class="p">:</span>
           <span class="c1"># 等待事件触发,返回触发事件</span>
           <span class="n">events</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">poll</span><span class="p">(</span><span class="no">TIMEOUT_PRECISION</span><span class="p">)</span>
       <span class="n">except</span> <span class="p">(</span><span class="no">OSError</span><span class="p">,</span> <span class="no">IOError</span><span class="p">)</span> <span class="n">as</span> <span class="ss">e</span><span class="p">:</span>
           <span class="k">if</span> <span class="n">errno_from_exception</span><span class="p">(</span><span class="n">e</span><span class="p">)</span> <span class="k">in</span> <span class="p">(</span><span class="n">errno</span><span class="o">.</span><span class="n">EPIPE</span><span class="p">,</span> <span class="n">errno</span><span class="o">.</span><span class="n">EINTR</span><span class="p">):</span>
               <span class="c1"># EPIPE: 当客户关闭连接时触发</span>
               <span class="c1"># EINTR: 收到信号时触发</span>
               <span class="c1"># 尽可能触发</span>
               <span class="n">asap</span> <span class="o">=</span> <span class="no">True</span>
               <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">&#39;poll:%s&#39;</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
           <span class="k">else</span><span class="p">:</span>
               <span class="n">logging</span><span class="o">.</span><span class="n">error</span><span class="p">(</span><span class="s1">&#39;poll:%s&#39;</span><span class="p">,</span> <span class="n">e</span><span class="p">)</span>
               <span class="n">traceback</span><span class="o">.</span><span class="n">print_exc</span><span class="p">()</span>
               <span class="n">continue</span>
       <span class="c1"># 找到事件对应的 handler,将事件交由它处理</span>
       <span class="k">for</span> <span class="n">sock</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="n">event</span> <span class="k">in</span> <span class="ss">events</span><span class="p">:</span>
           <span class="c1"># 通过 fd 找到对应的 handler</span>
           <span class="c1"># 一个 handler 可能对应多个 fd (reactor 模式)</span>
           <span class="c1"># 同步的将请求输入多路复用到 Request Handler</span>
           <span class="n">handler</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_fdmap</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="no">None</span><span class="p">)</span>
           <span class="k">if</span> <span class="n">handler</span> <span class="n">is</span> <span class="ow">not</span> <span class="ss">None</span><span class="p">:</span>
               <span class="n">handler</span> <span class="o">=</span> <span class="n">handler</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span>
               <span class="ss">try</span><span class="p">:</span>
                   <span class="c1"># handler 有三种可能</span>
                   <span class="c1"># TCPRelay, UDPRelay, DNSResolver</span>
                   <span class="n">handler</span><span class="o">.</span><span class="n">handle_event</span><span class="p">(</span><span class="n">sock</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span>
               <span class="n">except</span> <span class="p">(</span><span class="no">OSError</span><span class="p">,</span> <span class="no">IOError</span><span class="p">)</span> <span class="n">as</span> <span class="ss">e</span><span class="p">:</span>
                   <span class="n">shell</span><span class="o">.</span><span class="n">print_exception</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
       <span class="c1"># 计时器,10s 间隔调用注册的 handler_periodic 函数</span>
       <span class="n">now</span> <span class="o">=</span> <span class="n">time</span><span class="o">.</span><span class="n">time</span><span class="p">()</span>
       <span class="k">if</span> <span class="n">asap</span> <span class="ow">or</span> <span class="n">now</span> <span class="o">-</span> <span class="nb">self</span><span class="o">.</span><span class="n">_last_time</span> <span class="o">&gt;=</span> <span class="ss">TIMEOUT_PRECISION</span><span class="p">:</span>
           <span class="k">for</span> <span class="n">callback</span> <span class="k">in</span> <span class="nb">self</span><span class="o">.</span><span class="n">_periodic_callbacks</span><span class="p">:</span>
               <span class="n">callback</span><span class="p">()</span>
           <span class="nb">self</span><span class="o">.</span><span class="n">_last_time</span> <span class="o">=</span> <span class="n">now</span></code></pre></div>
<p><code>run</code> 方法是一个典型的 Event Loop 方法，通过 <code>poll</code> 来阻塞，等待事件发生，然后用事件对应的文件描述 <code>fd</code> 命中 <code>handler</code>，调用 <code>handler.handle_event(sock, fd, event)</code> 来将事件交由 <code>handler</code> 处理，同时间隔 <code>TIMEOUT_PRECISION</code> 秒调用 <code>TCPRelay</code>、<code>UDPRelay</code>、<code>DNSResolver</code> 的 <code>handler_periodic</code> 函数处理超时情况或是清理缓存。</p>
<p>看到这里，你肯定对 <code>TCPRelay</code> 对于事件封装产生了兴趣，我们重新回到 <code>tcprelay.py</code> 文件，来查看一下 <code>handle_event</code> 方法。</p>
<h3 id="tcprelaypy">tcprelay.py</h3>
<p>之前提到过，一个 <code>TCPRelay</code> 会对应监听指定的 Socket，并要分发到指定的 <code>TCPRelayHandler</code>，<code>eventloop.py</code> 的 <code>run</code> 方法中也能看到 <code>handler.handle_event(sock, fd, event)</code> 的调用。这个方法是如何实现的呢？</p>
<div class="highlight"><pre><code class="language-ruby" data-lang="ruby"><span class="k">def</span> <span class="nf">handle_event</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="n">sock</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span> <span class="n">event</span><span class="p">):</span>
   <span class="c1"># 处理 Event 并发送到对应的 handler</span>
   <span class="k">if</span> <span class="ss">sock</span><span class="p">:</span>
       <span class="n">logging</span><span class="o">.</span><span class="n">log</span><span class="p">(</span><span class="n">shell</span><span class="o">.</span><span class="n">VERBOSE_LEVEL</span><span class="p">,</span> <span class="s1">&#39;fd %d %s&#39;</span><span class="p">,</span> <span class="n">fd</span><span class="p">,</span>
                   <span class="n">eventloop</span><span class="o">.</span><span class="n">EVENT_NAMES</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">event</span><span class="p">,</span> <span class="n">event</span><span class="p">))</span>
   <span class="c1"># 如果是 TCPRelay 的 socket </span>
   <span class="c1"># 这时候说明有 TCP 连接,创建 TCPRelayHandler 并封装</span>
   <span class="k">if</span> <span class="n">sock</span> <span class="o">==</span> <span class="nb">self</span><span class="o">.</span><span class="n">_server_socket</span><span class="p">:</span>
       <span class="k">if</span> <span class="n">event</span> <span class="o">&amp;</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">POLL_ERR</span><span class="p">:</span>
           <span class="k">raise</span> <span class="no">Exception</span><span class="p">(</span><span class="s1">&#39;server_socket error&#39;</span><span class="p">)</span>
       <span class="ss">try</span><span class="p">:</span>
           <span class="n">logging</span><span class="o">.</span><span class="n">debug</span><span class="p">(</span><span class="s1">&#39;accept&#39;</span><span class="p">)</span>
           <span class="c1"># 接受连接</span>
           <span class="n">conn</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_server_socket</span><span class="o">.</span><span class="n">accept</span><span class="p">()</span>
           <span class="c1"># 完成 handler 封装</span>
           <span class="no">TCPRelayHandler</span><span class="p">(</span><span class="nb">self</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">_fd_to_handlers</span><span class="p">,</span>
                           <span class="nb">self</span><span class="o">.</span><span class="n">_eventloop</span><span class="p">,</span> <span class="n">conn</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">_config</span><span class="p">,</span>
                           <span class="nb">self</span><span class="o">.</span><span class="n">_dns_resolver</span><span class="p">,</span> <span class="nb">self</span><span class="o">.</span><span class="n">_is_local</span><span class="p">)</span>
       <span class="n">except</span> <span class="p">(</span><span class="no">OSError</span><span class="p">,</span> <span class="no">IOError</span><span class="p">)</span> <span class="n">as</span> <span class="ss">e</span><span class="p">:</span>
           <span class="n">error_no</span> <span class="o">=</span> <span class="n">eventloop</span><span class="o">.</span><span class="n">errno_from_exception</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
           <span class="k">if</span> <span class="n">error_no</span> <span class="k">in</span> <span class="p">(</span><span class="n">errno</span><span class="o">.</span><span class="n">EAGAIN</span><span class="p">,</span> <span class="n">errno</span><span class="o">.</span><span class="n">EINPROGRESS</span><span class="p">,</span>
                           <span class="n">errno</span><span class="o">.</span><span class="n">EWOULDBLOCK</span><span class="p">):</span>
               <span class="k">return</span>
           <span class="k">else</span><span class="p">:</span>
               <span class="n">shell</span><span class="o">.</span><span class="n">print_exception</span><span class="p">(</span><span class="n">e</span><span class="p">)</span>
               <span class="k">if</span> <span class="nb">self</span><span class="o">.</span><span class="n">_config</span><span class="o">[</span><span class="s1">&#39;verbose&#39;</span><span class="o">]</span><span class="p">:</span>
                   <span class="n">traceback</span><span class="o">.</span><span class="n">print_exc</span><span class="p">()</span>
   <span class="k">else</span><span class="p">:</span>
       <span class="k">if</span> <span class="ss">sock</span><span class="p">:</span>
           <span class="c1"># 找到 fd 对应的 TCPRelayHandler</span>
           <span class="n">handler</span> <span class="o">=</span> <span class="nb">self</span><span class="o">.</span><span class="n">_fd_to_handlers</span><span class="o">.</span><span class="n">get</span><span class="p">(</span><span class="n">fd</span><span class="p">,</span> <span class="no">None</span><span class="p">)</span>
           <span class="k">if</span> <span class="ss">handler</span><span class="p">:</span>
               <span class="c1"># 启用 handler 来处理读写事件</span>
               <span class="n">handler</span><span class="o">.</span><span class="n">handle_event</span><span class="p">(</span><span class="n">sock</span><span class="p">,</span> <span class="n">event</span><span class="p">)</span>
       <span class="k">else</span><span class="p">:</span>
           <span class="n">logging</span><span class="o">.</span><span class="n">warn</span><span class="p">(</span><span class="s1">&#39;poll removed fd&#39;</span><span class="p">)</span></code></pre></div>
<p>读写事件由 <code>EventLoop</code> 协调后分发给 <code>TCPRelay</code>，再经 <code>TCPRelay</code> 将事件下发给对应的 <code>TCPRelayHandler</code> 处理。那么这个“对应”该如何区别？这是都是 <em>fd(File Descriptor)</em> 的功劳。文件描述符 - fd 是内核为了高效管理已被打开的文件所创建的索引，通常用一个非负正数数来区分，用于指代被打开的文件，所有执行的 I/O 操作的系统都会通过文件描述符。</p>
<p>扩展来说，每个 Event 可以当做一个服务处理程序的时间。使用 epoll、select 等等方法以及向指定的 <code>handler</code> 投递意在实现<strong>解多路分配策略，并同步派发请求及相关请求来处理</strong>，这是 <em>Reactor Pattern</em> (反应器模式)的完美实现。说到这里不得说下 Reactor Pattern。</p>
<p>Reactor 的结构主要由<strong>资源</strong>，<strong>同步事件解多路器</strong>，<strong>分发器</strong>和<strong>请求处理器</strong>组成，并且该系统在原则上是存于单线程系统中。注明项目 <strong>Netty</strong> 就是基于该设计模式。通过 Reactor 的方式，将用户线程轮训 I/O 操作状态的工作统一交给了 <code>handler_event</code> 事件循环来进行处理。用户线程注册事件后处理器可以继续执行其他的工作，这就是单线程异步的体现，而 Reactor 线程负责调用内核的 select 函数检测 socket 状态，当 socket 被激活时，通知响应的 Client 线程执行对应的 <code>handler</code>。这里给出一张 I/O 多路复用的模型示意图：</p>
<p><img src="../assets/images/blog/15024138759233/IO-muti-road.png" alt="IO-muti-road" />
是不是中间的 Event Loop 感觉似曾相识呢？是的，我们的 <code>EventLoop</code> 与其的工作十分类似。在这里，我们完全不需要关注 I/O 的问题，因为这些都已经被封装好了。我们只需要知道，其 Event 传递以及击中对应的 Handler 就已经足够了。下面是 Shadowsocks 中 <code>EventLoop</code> 将 Event 发送至指定 Handler 的大体流程：</p>
<p><img src="../assets/images/blog/15024138759233/eventloop-flow.png" alt="eventloop-flo" />
我想，此时已经对 Shadowsocks 的事件处理有了一个最初步的认识。</p>
<h2 id="section-1">待续</h2>
<p>在笔者阅读 Shadowsocks 源码的时候，也巩固了之前计算机网络和 Linux kernel I/O Operation 有了更进一步的理解和认识。</p>
<p>之后我们将对 Shadowsocks 中对于 Event 处理进一步了解，并阅读 TCP Proxy 的相关源码。</p>
<h2 id="section-2">延伸及参考</h2>
<ul>
  <li><a href="https://loggerhead.me/posts/shadowsocks-yuan-ma-fen-xi-xie-yi-yu-jie-gou.html">Shadowsocks 源码分析——协议与结构</a></li>
  <li><a href="http://huiliu.github.io/2016/03/19/shadowsocks.html">shadowsocks源码分析：ssserver</a></li>
</ul>
  <footer class="post-footer">
    <section class="author">
      <h4>Desgard_Duan</h4>
      <p>I write many code to write less code.💻</p>
    </section>
<aside class="share">
  <h4>Share this.</h4>
  <a href="http://twitter.com/share?text=《Shadowsocks Probe I - Sock5 与 EventLoop 事件分发》 -- Guardia · 瓜地&amp;url=https://www.desgard.com/Shadowsocks-1/"
  onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-twitter-square"></i>
  </a>
  <a href="http://v.t.sina.com.cn/share/share.php?url=https://www.desgard.com/Shadowsocks-1/&amp;title=《Shadowsocks Probe I - Sock5 与 EventLoop 事件分发》 —— Guardia · 瓜地" onclick="window.open(this.href, 'twitter-share', 'width=550,height=235');return false;">
    <i class="fa fa-weibo"></i>
  </a>
</aside>
      <hr>
<aside id="comments" class="disqus">
  <div id="container"></div>
  <!-- <link rel="stylesheet" href="/assets/css/gitment.css">
  <script src="https://imsun.github.io/gitment/dist/gitment.browser.js"></script>
  <script>
  var gitment = new Gitment({
    id: "https://www.desgard.com/Shadowsocks-1/", 
    owner: 'Desgard',
    repo: 'desgard.github.com',
    oauth: {
      client_id: 'e2612df42f3f2a83e71c',
      client_secret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
    },
  })
  gitment.render('container')
  </script> -->
<link rel="stylesheet" href="/assets/css/gitalk.css">
<script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
<div id='gitalk-container'></div>
<script>
    const gitalk = new Gitalk({
        id: "https://www.desgard.com/Shadowsocks-1/", 
        clientID: 'e2612df42f3f2a83e71c',
        clientSecret: 'b53e85b314bb24a6d06773e48bbb62a4de3b8b3a',
        repo: 'desgard.github.com',
        owner: 'Desgard',
        admin: ['Desgard'],
        // facebook-like distraction free mode
        distractionFreeMode: false
    })
    gitalk.render('gitalk-container')
</script>
</aside>
  </footer>
</article>
    </main>
<footer class="footer">
  <div class="container">
    <ul class="icons">
      <li>
        <a href="https://github.com/desgard" class="icon-github" target="_blank">
          <i class="fa fa-github"></i>
        </a>
      </li>
      <li>
        <a href="https://www.facebook.com/desgard.duan" class="icon-facebook" target="_blank">
          <i class="fa fa-facebook"></i>
        </a>
      </li>
      <li>
        <a href="https://twitter.com/Desgard_Duan" class="icon-twitter" target="_blank">
          <i class="fa fa-twitter"></i>
        </a>
      </li>
      <li>
        <a href="https://stackoverflow.com/users/6119149/desgard-duan" class="icon-github" target="_blank">
          <i class="fa fa-stack-overflow"></i>
        </a>
      </li>
      <li>
        <a href="https://weibo.com/desgard" class="icon-instagram" target="_blank">
          <i class="fa fa-weibo"></i>
        </a>
      </li>
    </ul>
    <p>
      <q>I write many code to write less code.💻</q>
      <small>– Gua</small>
    </p>
    <small class="clearfix">
      Powered by <a href="http://jekyllrb.com" target="_blank">Jekyll</a> • <a href="https://github.com/desgard" target="_blank">Open source <i class="fa fa-heart"></i></a>
    </small>
  </div>
</footer>
<a class="scroll-up fa fa-chevron-up bounce" href="#" data-position="0"></a>
<div id="modalSearch" class="modal">
  <div class="modal__overlay"></div>
  <div class="modal__content">
    <a href="#!" class="modal-close" data-modal-close>&times;</a>
    <div class="search-container">
      <input type="text" id="search-input" placeholder="Search articles">
      <ul id="results-container"></ul>
    </div>
  </div>
</div>
    <script src="/assets/main-52d417e8a6ff9f5b168386d37c96338a.js"></script>
  </body>
  <script>
    var link = "" ;
    var os = function() {  
      var ua = navigator.userAgent,  
      isWindowsPhone = /(?:Windows Phone)/.test(ua),  
      isSymbian = /(?:SymbianOS)/.test(ua) || isWindowsPhone,   
      isAndroid = /(?:Android)/.test(ua),   
      isFireFox = /(?:Firefox)/.test(ua),   
      isChrome = /(?:Chrome|CriOS)/.test(ua),  
      isTablet = /(?:iPad|PlayBook)/.test(ua) || (isAndroid && !/(?:Mobile)/.test(ua)) || (isFireFox && /(?:Tablet)/.test(ua)),  
      isPhone = /(?:iPhone)/.test(ua) && !isTablet,  
      isPc = !isPhone && !isAndroid && !isSymbian;  
      return {  
        isTablet: isTablet,  
        isPhone: isPhone,  
        isAndroid : isAndroid,  
        isPc : isPc  
      };  
    }();  
    if (link.length > 0) {
      if (os.isAndroid || os.isPhone) {
        location.replace(link);
      }
    }
  </script>
</html>